package com.dev.hikmachine.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.dev.hikmachine.constant.HCNetSDK;
import com.dev.hikmachine.constant.HikConstant;
import com.dev.hikmachine.entity.access.AccessPeople;
import com.dev.hikmachine.entity.access.RightPlan;
import com.dev.hikmachine.entity.access.UserInfoSearch;
import com.dev.hikmachine.entity.access.Valid;
import com.dev.hikmachine.param.AccessControlUser;
import com.dev.hikmachine.rtsp.HikDevResponse;
import com.dev.hikmachine.rtsp.QueryRequest;
import com.dev.hikmachine.service.HikAccessControlService;
import com.dev.hikmachine.service.HikDevService;
import com.dev.hikmachine.structure.*;
import com.dev.hikmachine.util.ConfigJsonUtil;
import com.dev.hikmachine.util.StringEncodingUtil;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author wisnky
 * @date 2024-4-17 14:49
 */

@Slf4j
@Service
@RequiredArgsConstructor
public class HikAccessControlServiceImpl implements HikAccessControlService {

    private final HikDevService hikDevService;

    /**
     * 通过ip查询所有卡信息
     *
     * @param ip 设备ip
     * @return
     */
    @Override
    public HikDevResponse getAllCardInfo(String ip) {

        HikDevResponse result = new HikDevResponse();
        if (StrUtil.isBlank(ip)) {
            result.err("缺少必要参数字段：ip");
            return result;
        }
        Integer longUserId = ConfigJsonUtil.getDeviceSearchInfoByIp(ip).getLoginId();
        if (longUserId < 0) {
            return result.err("设备未注册");
        }
        NET_DVR_CARD_COND strCardCond = new NET_DVR_CARD_COND();
        strCardCond.read();
        strCardCond.dwSize = strCardCond.size();
        //查询所有
        strCardCond.dwCardNum = 0xffffffff;
        strCardCond.write();
        Pointer ptrStruCond = strCardCond.getPointer();

        //下发卡长连接句柄
        int setCardConfigHandle = hikDevService.NET_DVR_StartRemoteConfig(longUserId, HCNetSDK.NET_DVR_GET_CARD, ptrStruCond, strCardCond.size(), null, null);
        if (setCardConfigHandle == -1) {
            result.err("建立下发卡长连接失败，错误码为" + hikDevService.NET_DVR_GetLastError());
            return result;
        }

        NET_DVR_CARD_RECORD struCardRecord = new NET_DVR_CARD_RECORD();
        struCardRecord.read();
        struCardRecord.dwSize = struCardRecord.size();
        struCardRecord.write();

        IntByReference pInt = new IntByReference(0);
        int iCharEncodeType = ConfigJsonUtil.getDeviceSearchInfoByIp(ip).getCharEncodeType();
        List<Map<String, Object>> list = new ArrayList<>();
        while (true) {
            //下发卡数据状态
            int dwState = hikDevService.NET_DVR_GetNextRemoteConfig(setCardConfigHandle, struCardRecord.getPointer(), struCardRecord.size());
            struCardRecord.read();
            if (dwState == -1) {
                result.err("NET_DVR_SendWithRecvRemoteConfig接口调用失败，错误码：" + hikDevService.NET_DVR_GetLastError());
                break;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_NEED_WAIT) {
                log.info("配置等待");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_FAILED) {
                result.err("获取卡参数失败");
                break;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_EXCEPTION) {
                result.err("获取卡参数异常");
                break;
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_SUCCESS) {
                //姓名
                String strName = StringEncodingUtil.guessEncodingTransformString(struCardRecord.byName);
                //卡号
                String cardNo = new String(struCardRecord.byCardNo).trim();
                //卡类型
                byte cardType = struCardRecord.byCardType;
                Map<String, Object> map = new HashMap<>(3);
                map.put("strName", strName);
                map.put("cardNo", cardNo);
                map.put("cardType", cardType);
                map.put("intEmployeeNo", struCardRecord.dwEmployeeNo);
                list.add(map);
            } else if (dwState == HCNetSDK.NET_SDK_CONFIG_STATUS_FINISH) {
                break;
            }
        }
        if (list.size() > 0) {
            result.ok("获取卡信息成功", list);
        }
        if (!hikDevService.NET_DVR_StopRemoteConfig(setCardConfigHandle)) {
            result.err("NET_DVR_StopRemoteConfig接口调用失败，错误码：" + hikDevService.NET_DVR_GetLastError());
        }
        return result;
    }

    /**
     * 通过ip、工号查询所有员工信息
     *
     * @param ip           设备IP
     * @param employeeNos  用户id
     * @param queryRequest 分页参数
     * @return
     */
    @Override
    public HikDevResponse getAllUserInfo(String ip, String[] employeeNos, QueryRequest queryRequest) {
        String urlInBuffer = "POST /ISAPI/AccessControl/UserInfo/Search?format=json";
        //=====================================================================
        //组装查询的JSON报文，这边查询的是所有的卡
        JSONObject jsonObject = new JSONObject();
        JSONObject jsonSearchCond = new JSONObject();

        //如果需要查询指定的工号人员信息 EmployeeNoList可选，人员 ID 列表
        if (employeeNos.length > 0) {
            jsonSearchCond.put("EmployeeNoList", packageUserNos(employeeNos));
        }
        //必填，string，搜索记录唯一标识，用来确认上层客户端是否为同一个（倘若是同一个,设备记录内存,下次搜索加快速度）暂时写死吧，反正同一个查询还快
        jsonSearchCond.put("searchID", "123e4567-e89b-12d3-a456-426655440000");
        // 必填，integer，查询结果在结果列表中的起始位置。当记录条数很多时，一次查询不能获取所有的记录，下一次查询时指定位置可以查询后面的记录
        // （若设备支持的最大 totalMatches 为 M 个，但是当前设备已存储的 totalMatches 为 N 个（N<=M），则该字段的合法范围为 0~N-1）
        jsonSearchCond.put("searchResultPosition", 10 * (queryRequest.getPageNum() - 1));
        //必填，integer，本次协议调用可获取的最大记录数（如maxResults 值大于设备能力集返回的范围，则设备按照能力集最大值返回，设备不进行报错）
        jsonSearchCond.put("maxResults", queryRequest.getPageSize());
        jsonObject.put("UserInfoSearchCond", jsonSearchCond);

        String strInBuff = jsonObject.toJSONString();
        //=====================================================================
        BYTE_ARRAY ptrInBuff = new BYTE_ARRAY(strInBuff.length());
        ptrInBuff.byValue = strInBuff.getBytes(StandardCharsets.UTF_8);
        ptrInBuff.write();
        JSONObject result = this.userInfo(ip, urlInBuffer, ptrInBuff.getPointer(), strInBuff.length(), 2550, 1024 * 10);

        //因为name是字节数组，所以需要自己转一下
        UserInfoSearch userInfoSearch = result.getJSONObject("data").getJSONObject("UserInfoSearch").to(UserInfoSearch.class);
//        log.info("{}", userInfoSearch);
//        if (null != userInfoSearch.getUserInfo() && userInfoSearch.getUserInfo().size() > 0) {
//            userInfoSearch.getUserInfo().forEach(people -> {
//                try {
//                    //这里必须加上双引号反序列化，因为在序列化的时候是json带双引号
//                    String nameByte = "\"" + people.getRealName() + "\"";
//                    //如果realName是json序列化的字符串，那么需要反序列化
//                    byte[] bytes = JSON.parseObject(nameByte, byte[].class);
//                    String realName = new String(bytes).trim();
//                    people.setRealName(realName);
//                } catch (ArrayIndexOutOfBoundsException ignored) {
//                    // 汉字是越界异常超出256，不加上引号是expect '[', but error, pos 1, line 1, column 2
//                    // 已经是汉字的名字，也就是说他们的名字是从设备上直接录制的，并没有走sdk，
//                    // 这时候就会反序列化错误，那么我们就直接忽略这些错误，把realName原样输出
//                }
//
//            });
//        }
        userInfoSearch.setPageNum(queryRequest.getPageNum());
        return new HikDevResponse().ok(result.getString("msg"), userInfoSearch);
    }

    /**
     * 工号数组转换为Map列表
     *
     * @param employeeNos
     * @return
     */
    private List<Map<String, Object>> packageUserNos(String[] employeeNos) {
        List<Map<String, Object>> employeeNoList = new ArrayList<>();
        for (String no : employeeNos) {
            Map<String, Object> employeeNo = new HashMap<>(1);
            //employeeNo可选，string，人员 ID
            employeeNo.put("employeeNo", no);
            employeeNoList.add(employeeNo);
        }
        return employeeNoList;
    }

    /**
     * 数据信息添加进入设备
     *
     * @param ip
     * @param accessControlUser 门禁用户: ip, realName, int:employeeNo
     * @return
     */
    @Override
    public HikDevResponse addUser(String ip, AccessControlUser accessControlUser) {
        String urlInBuffer = "POST /ISAPI/AccessControl/UserInfo/Record?format=json";
        AccessPeople accessPeople = new AccessPeople();
        accessPeople.setRealName(accessControlUser.getRealName());
        accessPeople.setEmployeeNo(accessControlUser.getEmployeeNo());
        JSONObject result = this.aboutUserInfo(ip, accessPeople, urlInBuffer);
        return new HikDevResponse().ok(result.getString("msg"));
    }

    /**
     *通过ip、员工信息修改用户信息
     *
     * @param ip
     * @param accessPeople 门禁用户: ip, realName, int:employeeNo
     * @return
     */
    @Override
    public HikDevResponse modifyUser(String ip, AccessPeople accessPeople) {
        String urlInBuffer = "PUT /ISAPI/AccessControl/UserInfo/Modify?format=json";
        JSONObject result = this.aboutUserInfo(ip, accessPeople, urlInBuffer);
        return new HikDevResponse().ok(result.getString("msg"));
    }

    /**
     * 通过ip、信息批量添加用户进入设备
     *
     * @param ip
     * @param accessControlUserList 门禁用户列表: ip, realName, int:employeeNo
     * @return
     */
    @Override
    public HikDevResponse addMultiUser(String ip, List<AccessControlUser> accessControlUserList) {
        for (AccessControlUser user : accessControlUserList) {
            this.addUser(ip, user);
        }
        return new HikDevResponse().ok("批量下发用户完成");
    }

    /**下发人脸
     *
     * @param ip                门禁ip
     * @param accessControlUser 门禁用户: employeeNo, base64Pic
     * @return
     */
    @Override
    public HikDevResponse addUserFace(String ip, AccessControlUser accessControlUser) {
        String urlInBuffer = "POST /ISAPI/Intelligent/FDLib/FaceDataRecord?format=json";
        HikDevResponse result = new HikDevResponse();
        Integer lHandler = this.startRemoteConfig(ip, urlInBuffer, 2551);
        if (lHandler < 0) {
            return result.err("NET_DVR_StartRemoteConfig 接口调用失败，错误码：" + this.hikDevService.NET_DVR_GetLastError());
        }
        NET_DVR_JSON_DATA_CFG strAddFaceDataCfg = new NET_DVR_JSON_DATA_CFG();
        strAddFaceDataCfg.read();
        strAddFaceDataCfg.dwSize = strAddFaceDataCfg.size();
        //字符串拷贝到数组中
        BYTE_ARRAY ptrByteArray = new BYTE_ARRAY(1024);

        Integer employeeNo = accessControlUser.getEmployeeNo();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("faceLibType", "blackFD");
        jsonObject.put("FDID", "1");
        //人脸下发关联的工号
        jsonObject.put("FPID", employeeNo);
        String strJsonData = jsonObject.toString();
        System.out.println("下发人脸的json报文:" + strJsonData);
        System.arraycopy(strJsonData.getBytes(), 0, ptrByteArray.byValue, 0, strJsonData.length());
        ptrByteArray.write();
        strAddFaceDataCfg.lpJsonData = ptrByteArray.getPointer();
        strAddFaceDataCfg.dwJsonDataSize = strJsonData.length();

        /*****************************************
         * 从本地文件里面读取JPEG图片二进制数据
         *****************************************/
        //下发的人脸图片
        String base64Pic = accessControlUser.getBase64Pic();
        byte[] decode = Base64.decode(base64Pic);
        //转化为输入流
        ByteArrayInputStream picfile = new ByteArrayInputStream(decode);
        int picdataLength = picfile.available();
        if (picdataLength < 0) {
            System.out.println("input file dataSize < 0");
        }
        BYTE_ARRAY ptrpicByte = new BYTE_ARRAY(picdataLength);
        try {
            picfile.read(ptrpicByte.byValue);
            picfile.close();
        } catch (IOException e2) {
            e2.printStackTrace();
        }
        ptrpicByte.write();
        strAddFaceDataCfg.dwPicDataSize = picdataLength;
        strAddFaceDataCfg.lpPicData = ptrpicByte.getPointer();
        strAddFaceDataCfg.write();

        BYTE_ARRAY ptrOutBuff = new BYTE_ARRAY(1024);
        IntByReference pInt = new IntByReference(0);
        int dwState = this.hikDevService.NET_DVR_SendWithRecvRemoteConfig(lHandler,
                strAddFaceDataCfg.getPointer(), strAddFaceDataCfg.dwSize,
                ptrOutBuff.getPointer(), ptrOutBuff.size(), pInt);
        //读取返回的json并解析
        ptrOutBuff.read();
        String strResult = new String(ptrOutBuff.byValue).trim();
        JSONObject jsonResult = JSONObject.parseObject(strResult);
        result.put("data", jsonResult);
        if (dwState != HCNetSDK.NET_SDK_CONFIG_STATUS_SUCCESS) {
            //返回NET_SDK_CONFIG_STATUS_SUCCESS代表流程走通了，但并不代表下发成功，比如有些设备可能因为人员已存在等原因下发失败，所以需要解析Json报文 如果statusCode=1无异常情况,否则就是有异常情况
            result.err("NET_DVR_SendWithRecvRemoteConfig接口调用失败，错误码：" + this.hikDevService.NET_DVR_GetLastError());
        }
        // TODO 下发人员时：dwState其实不会走到这里，因为设备不知道我们会下发多少个人，所以长连接需要我们主动关闭
        if (!this.hikDevService.NET_DVR_StopRemoteConfig(lHandler)) {
            result.err("NET_DVR_StopRemoteConfig接口调用失败，错误码：" + this.hikDevService.NET_DVR_GetLastError());
        }
        return result;
    }

    /**
     * 批量下发人脸信息
     *
     * @param ip                    门禁ip
     * @param accessControlUserList 门禁用户列表: employeeNo, base64Pic
     * @return
     */
    @Override
    public HikDevResponse addMultiUserFace(String ip, List<AccessControlUser> accessControlUserList) {
        for (AccessControlUser user : accessControlUserList) {
            this.addUserFace(ip, user);
        }
        return new HikDevResponse().ok("批量下发人脸完成");
    }

    /**
     * 批量删除人脸
     *
     * @param ip          设备ip
     * @param employeeIds 多个用户工号，用逗号隔开
     * @return
     */
    @Override
    public HikDevResponse delMultiUserFace(String ip, String[] employeeIds) {
        String urlInBuffer = "PUT /ISAPI/Intelligent/FDLib/FDSearch/Delete?format=json&FDID=1&faceLibType=blackFD";
        JSONArray array = new JSONArray();
        //================
        for (String employeeId : employeeIds) {
            JSONObject valueObj = new JSONObject();
            valueObj.put("value", employeeId);
            array.add(valueObj);
        }
        JSONObject fpid = new JSONObject();
        fpid.put("FPID", array);
        String strInBuffer = fpid.toJSONString();
        //=============
        return deleteOperation(ip, urlInBuffer, strInBuffer);
    }


    /**
     * 批量删除用户
     *
     * @param ip          设备ip
     * @param employeeIds 多个用户工号，用逗号隔开
     * @return
     */
    @Override
    public HikDevResponse delMultiUser(String ip, String[] employeeIds) {
        String urlInBuffer = "PUT /ISAPI/AccessControl/UserInfo/Delete?format=json";
        JSONArray array = new JSONArray();
        //=====================
        for (String employeeId : employeeIds) {
            JSONObject employeeNo = new JSONObject();
            employeeNo.put("employeeNo", employeeId);
            array.add(employeeNo);
        }
        JSONObject employeeNoList = new JSONObject();
        employeeNoList.put("EmployeeNoList", array);
        JSONObject userInfoDelCond = new JSONObject();
        userInfoDelCond.put("UserInfoDelCond", employeeNoList);
        String strInBuffer = userInfoDelCond.toJSONString();
        //======================
        return deleteOperation(ip, urlInBuffer, strInBuffer);
    }


    private JSONObject userInfo(String ip, String urlInBuffer, Pointer lpInBuff, int dwInBuffSize, int dwCommand, int iOutBuffLen) {
        JSONObject result = new JSONObject();
        Integer lHandler = this.startRemoteConfig(ip, urlInBuffer, dwCommand);
        if (lHandler < 0) {
            result.put("msg", "NET_DVR_StartRemoteConfig 接口调用失败，错误码：" + this.hikDevService.NET_DVR_GetLastError());
            return result;
        }
        BYTE_ARRAY ptrOutBuff = new BYTE_ARRAY(iOutBuffLen);
        IntByReference pInt = new IntByReference(0);
        int dwState = this.hikDevService.NET_DVR_SendWithRecvRemoteConfig(lHandler, lpInBuff, dwInBuffSize, ptrOutBuff.getPointer(), iOutBuffLen, pInt);
        //读取返回的json并解析
        ptrOutBuff.read();
        String strResult = new String(ptrOutBuff.byValue).trim();
        JSONObject jsonResult = JSONObject.parseObject(strResult);
        result.put("data", jsonResult);
        if (dwState != HCNetSDK.NET_SDK_CONFIG_STATUS_SUCCESS) {
            //返回NET_SDK_CONFIG_STATUS_SUCCESS代表流程走通了，但并不代表下发成功，比如有些设备可能因为人员已存在等原因下发失败，所以需要解析Json报文 如果statusCode=1无异常情况,否则就是有异常情况
            result.put("msg", "NET_DVR_SendWithRecvRemoteConfig接口调用失败，错误码：" + this.hikDevService.NET_DVR_GetLastError());
        }
        // TODO 下发人员时：dwState其实不会走到这里，因为设备不知道我们会下发多少个人，所以长连接需要我们主动关闭
        if (!this.hikDevService.NET_DVR_StopRemoteConfig(lHandler)) {
            result.put("msg", "NET_DVR_StopRemoteConfig接口调用失败，错误码：" + this.hikDevService.NET_DVR_GetLastError());
        }
        return result;
    }


    private Integer startRemoteConfig(String ip, String urlInBuffer, int dwCommand) {
        // 获取用户句柄

        Integer longUserId = Objects.requireNonNull(ConfigJsonUtil.getDeviceSearchInfoByIp(ip)).getLoginId();
        if (null != longUserId && longUserId != -1) {
            //  数组
            BYTE_ARRAY ptrByteArray = new BYTE_ARRAY(1024);
            ptrByteArray.byValue = urlInBuffer.getBytes();
            ptrByteArray.write();

            return this.hikDevService.NET_DVR_StartRemoteConfig(longUserId, dwCommand, ptrByteArray.getPointer(), urlInBuffer.length(), null, null);
        }
        return -1;
    }


    private JSONObject aboutUserInfo(String ip, AccessPeople people, String urlInBuffer) {
        //name需要转为字节数组 将中文字符编码之后用数组拷贝的方式，避免因为编码导致的长度问题
//        people.setName(people.getRealName().getBytes(StandardCharsets.UTF_8));
        people.setName(people.getRealName());
        people.setValid(new Valid());
        List<RightPlan> rightPlanList = new ArrayList<>();
        rightPlanList.add(new RightPlan());
        people.setRightPlan(rightPlanList);
        JSONObject userInfoBuff = new JSONObject();
        userInfoBuff.put("UserInfo", people);
        String userInfoBuffString = userInfoBuff.toJSONString();
        BYTE_ARRAY ptrInBuff = new BYTE_ARRAY(userInfoBuffString.length());
        ptrInBuff.byValue = userInfoBuffString.getBytes(StandardCharsets.UTF_8);
        ptrInBuff.write();
        return this.userInfo(ip, urlInBuffer, ptrInBuff.getPointer(), userInfoBuffString.length(), 2550, 1024);
    }

    private HikDevResponse deleteOperation(String ip, String urlInBuffer, String strInBuffer) {
        BYTE_ARRAY ptrUrl = new BYTE_ARRAY(HikConstant.BYTE_ARRAY_LEN);
        ptrUrl.byValue = urlInBuffer.getBytes(StandardCharsets.UTF_8);
        ptrUrl.write();

        //输入条件
        log.info("打印json封装：{}", strInBuffer);
        BYTE_ARRAY ptrInBuffer = new BYTE_ARRAY(HikConstant.ISAPI_DATA_LEN);
        ptrInBuffer.read();
        ptrInBuffer.byValue = strInBuffer.getBytes(StandardCharsets.UTF_8);
        ptrInBuffer.write();

        // 获取用户句柄
        Integer longUserId = ConfigJsonUtil.getDeviceSearchInfoByIp(ip).getLoginId();

        NET_DVR_XML_CONFIG_INPUT strXMLInput = new NET_DVR_XML_CONFIG_INPUT();
        strXMLInput.read();
        strXMLInput.dwSize = strXMLInput.size();
        strXMLInput.lpRequestUrl = ptrUrl.getPointer();
        strXMLInput.dwRequestUrlLen = ptrUrl.byValue.length;
        strXMLInput.lpInBuffer = ptrInBuffer.getPointer();
        strXMLInput.dwInBufferSize = ptrInBuffer.byValue.length;
        strXMLInput.write();
        BYTE_ARRAY ptrStatusByte = new BYTE_ARRAY(HikConstant.ISAPI_STATUS_LEN);
        ptrStatusByte.read();
        BYTE_ARRAY ptrOutByte = new BYTE_ARRAY(HikConstant.ISAPI_DATA_LEN);
        ptrOutByte.read();
        NET_DVR_XML_CONFIG_OUTPUT strXMLOutput = new NET_DVR_XML_CONFIG_OUTPUT();
        strXMLOutput.read();
        strXMLOutput.dwSize = strXMLOutput.size();
        strXMLOutput.lpOutBuffer = ptrOutByte.getPointer();
        strXMLOutput.dwOutBufferSize = ptrOutByte.size();
        strXMLOutput.lpStatusBuffer = ptrStatusByte.getPointer();
        strXMLOutput.dwStatusSize = ptrStatusByte.size();
        strXMLOutput.write();

        HikDevResponse result = new HikDevResponse();
        if (!this.hikDevService.NET_DVR_STDXMLConfig(longUserId, strXMLInput, strXMLOutput)) {
            int iErr = this.hikDevService.NET_DVR_GetLastError();
            return result.err("NET_DVR_STDXMLConfig失败，错误号：" + iErr);
        } else {
            strXMLOutput.read();
            ptrOutByte.read();
            ptrStatusByte.read();
            String strOutXML = new String(ptrOutByte.byValue).trim();
            String strStatus = new String(ptrStatusByte.byValue).trim();
            JSONObject strJson = new JSONObject();
            strJson.put("strOutXMl", JSONObject.parseObject(strOutXML));
            strJson.put("strStatus", JSONObject.parseObject(strStatus));
            result.ok("操作成功", strJson);
            log.info("输出结果:" + strJson);
        }
        return result;
    }


}
